module.exports = function evaluatePage() {
  const ELEMENTS = [
    "audio",
    "button",
    "canvas",
    "code",
    "img",
    "input",
    "pre",
    "svg",
    "textarea",
    "video",
    "xmp"
  ];
  const blocks = [];
  const win_w = window.innerWidth;
  const win_h = window.innerHeight;
  const agrs = parse(arguments[0]);

  const classProps = {
    position: "fixed",
    zIndex: 2,
    background: agrs.background
  };
  if (agrs.animation) {
    classProps.animation = agrs.animation;
  }

  createCommonClass(classProps);

  function parse(value) {
    let val = {};
    Object.entries(value).forEach(i => {
      const [k, v] = i;
      const [key, type] = k.split("-");
      if (type === "function") {
        val[key] = eval("(" + v + ")");
      } else {
        val[key] = v;
      }
    });
    return val;
  }

  function drawBlock({
    width,
    height,
    top,
    left,
    zIndex = 2,
    background = agrs.background,
    radius,
    subClas
  } = {}) {
    const styles = ["height:" + height + "%"];

    if (!subClas) {
      styles.push(
        "top:" + top + "%",
        "left:" + left + "%",
        "width:" + width + "%"
      );
    }

    if (classProps.zIndex !== zIndex) {
      styles.push("z-index:" + zIndex);
    }

    if (classProps.background !== background) {
      styles.push("background:" + background);
    }

    radius && radius != "0px" && styles.push("border-radius:" + radius);
    blocks.push(
      `<div class="_${subClas ? " __" : ""}" style="${styles.join(";")}"></div>`
    );
  }

  function wPercent(x) {
    return parseFloat((x / win_w) * 100).toFixed(3);
  }

  function hPercent(x) {
    return parseFloat((x / win_h) * 100).toFixed(3);
  }

  function getType(arg) {
    return Object.prototype.toString
      .call(arg)
      .toLowerCase()
      .match(/\s(\w+)/)[1];
  }

  function getStyle(node, attr) {
    return (node.nodeType === 1 ? getComputedStyle(node)[attr] : "") || "";
  }

  function getRootNode(el) {
    if (!el) return el;
    return typeof el === "object"
      ? el
      : getType(el) === "string"
      ? document.querySelector(el)
      : null;
  }

  function includeNode(elements, node) {
    return ~elements.indexOf((node.tagName || "").toLowerCase());
  }

  function isHideStyle(node) {
    if (
      getStyle(node, "display") === "none" ||
      getStyle(node, "visibility") === "hidden" ||
      getStyle(node, "opacity") == 0 ||
      node.hidden
    ) {
      return true;
    }
    const { t, l, w, h } = getRect(node);
    if (l < -w || t < -h) {
      return true;
    }
  }

  function isCustomCardBlock(node) {
    const bgStyle = getStyle(node, "background");
    const bgColorReg = /rgba\([\s\S]+?0\)/gi;
    const bdReg = /(0px)|(none)/;
    const hasBgColor =
      !bgColorReg.test(bgStyle) || ~bgStyle.indexOf("gradient");
    const hasNoBorder = ["top", "left", "right", "bottom"].some(item => {
      return bdReg.test(getStyle(node, "border-" + item));
    });
    const { w, h } = getRect(node);
    const customCardBlock = !!(
      hasBgColor &&
      (!hasNoBorder || getStyle(node, "box-shadow") != "none") &&
      w > 0 &&
      h > 0 &&
      w < 0.95 * win_w &&
      h < 0.3 * win_h
    );
    return customCardBlock;
  }

  function getRect(node) {
    if (!node) return {};
    const {
      top: t,
      left: l,
      width: w,
      height: h
    } = node.getBoundingClientRect();
    return {
      t,
      l,
      w,
      h
    };
  }

  function createCommonClass(styles) {
    const inlineStyle = ["<style>._{"];
    for (let key in styles) {
      inlineStyle.push(`${key === "zIndex" ? "z-index" : key}:${styles[key]};`);
    }
    inlineStyle.push("}.__{top:0%;left:0%;width:100%;}</style>");
    blocks.push(inlineStyle.join(""));
  }

  function inHeader(node) {
    if (agrs.header) {
      const height = parseInt(agrs.header.height);
      if (height) {
        const { t } = getRect(node);
        return t <= height;
      }
    }
  }

  class DrawSkeleton {
    constructor(option) {
      this.rootNode = getRootNode(option.rootNode) || document.body;
      this.offsetTop = option.offsetTop || 0;
      this.includeNode = option.includeNode;
      this.beforeDraw = option.beforeDraw;
      this.devtools = option.devtools;
      this.headless = option.headless;
      return this instanceof DrawSkeleton ? this : new DrawSkeleton(option);
    }

    drawHeader() {
      if (agrs.header) {
        const { height, background = agrs.background } = agrs.header;
        const hHeight = parseInt(height);
        if (hHeight) {
          drawBlock({
            background,
            zIndex: 2,
            height: hPercent(hHeight),
            subClas: true
          });
        }
      }
    }

    showBlocks() {
      const blocksHTML = blocks.join("");
      if (blocks.length > 0 && this.devtools) {
        const { body } = document;
        let skeletonContainer = document.querySelector(
          "#create-skeleton-container"
        );
        if (!skeletonContainer) {
          skeletonContainer = document.createElement("div");
          skeletonContainer.id = "create-skeleton-container";
          body.appendChild(skeletonContainer);
        }
        skeletonContainer.innerHTML = blocksHTML;

        document.body.style.overflow = this.originStyle.bodyOverflow;
        window.scrollTo(0, this.originStyle.scrollTop);
      }
      return blocksHTML;
    }

    // 手动点击骨架屏按钮生成骨架屏
    async manualPainting() {
      return new Promise(resolve => {
        // 支持在打开页面的时候手动点击创建骨架屏
        const { body } = document;
        const button = document.createElement("button");
        button.style.cssText = `position: fixed; right:0; top:0; z-index: 999999; background:#1890ff; color:#FFFFFF; font-size: 10px; border-radius: 2px;`;
        button.innerHTML = "骨架屏";
        button.id = "create-skeleton-btn";
        button.onclick = () => resolve(this.start());
        body.appendChild(button);
      });
    }

    async init() {
      this.originStyle = {
        scrollTop: window.scrollY,
        bodyOverflow: getStyle(document.body, "overflow")
      };
      window.scrollTo(0, this.offsetTop);
      document.body.style.cssText += "overflow:hidden!important;";

      if (getType(this.beforeDraw) === "function") {
        this.beforeDraw(this);
      }

      drawBlock({
        height: 100,
        zIndex: 1,
        background: "#fff",
        subClas: true
      });

      if (this.headless === false) {
        return await this.manualPainting();
      }

      return this.start();
    }

    start() {
      const root = this.rootNode;
      this.drawHeader();
      const filter = {
        acceptNode(node) {
          if (isHideStyle(node)) {
            return NodeFilter.FILTER_REJECT;
          }

          if (node.id === "create-skeleton-btn") {
            return NodeFilter.FILTER_SKIP;
          }

          if (
            includeNode(ELEMENTS, node) ||
            getStyle(node, "backgroundImage").match(/url\(.+?\)/) ||
            (isCustomCardBlock(node) && !inHeader(node)) ||
            [].find.call(
              node.childNodes,
              n => n.nodeType === 3 && n.textContent.trim()
            )
          ) {
            return NodeFilter.FILTER_ACCEPT;
          }

          const { t, l, w, h } = getRect(node);

          if (
            !(
              w > 0 &&
              h > 0 &&
              l >= 0 &&
              l < win_w &&
              win_h - t >= 20 &&
              t >= 0
            )
          ) {
            return NodeFilter.FILTER_SKIP;
          }

          return NodeFilter.FILTER_SKIP;
        }
      };

      const nodeIterator = document.createTreeWalker(
        root,
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
        filter
      );

      let node;
      while ((node = nodeIterator.nextNode())) {
        const { t, l, w, h } = getRect(node);
        if (this.includeNode?.(node, drawBlock) !== false) {
          drawBlock({
            width: wPercent(w),
            height: hPercent(h),
            top: hPercent(t),
            left: wPercent(l),
            radius: getStyle(node, "border-radius")
          });
        }
      }

      return this.showBlocks();
    }
  }

  return new Promise((resolve, reject) => {
    try {
      const html = new DrawSkeleton(agrs).init();
      resolve(html);
    } catch (e) {
      reject(e);
    }
  });
};
